package com.wangsan.study.web.security;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.RequestMapping;

import com.wangsan.study.security.AuthMapping;
import com.wangsan.study.security.ResourceAuth;
import com.wangsan.study.security.ResourceAuthItem;
import com.wangsan.study.security.ResourceAuthResolver;

/**
 * ResourceAuthResolver,让spring mvc扫描
 *
 * @author wangsan
 * @date 2016/12/29
 */
@Component
public class AnnotationResourceAuthResolver implements InitializingBean, ApplicationContextAware, ResourceAuthResolver {
    private ApplicationContext applicationContext;
    private List<ResourceAuthItem> resourceAuthItemList = new ArrayList<>();

    @Override
    public void afterPropertiesSet() throws Exception {
        String[] beanNames = applicationContext.getBeanNamesForAnnotation(Controller.class);

        for (String beanName : beanNames) {
            Class<?> handlerType = applicationContext.getType(beanName);
            final Class<?> userType = ClassUtils.getUserClass(handlerType);

            Map<Method, List<ResourceAuthItem>> methods = MethodIntrospector.selectMethods(userType,
                    (MethodIntrospector.MetadataLookup<List<ResourceAuthItem>>) method -> {
                        return createRaForMethod(method);
                    });

            List<ResourceAuthItem> resourceAuthItems =
                    methods.values().stream().flatMap(itemList -> itemList.stream()).collect(Collectors.toList());
            resourceAuthItemList.addAll(resourceAuthItems);
        }
    }

    private List<ResourceAuthItem> createRaForMethod(Method method) {
        List<ResourceAuthItem> itemList = new ArrayList<>();
        RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
        ResourceAuth resourceAuth = AnnotatedElementUtils.findMergedAnnotation(method, ResourceAuth.class);
        AuthMapping authMapping = AnnotatedElementUtils.findMergedAnnotation(method, AuthMapping.class);

        if (requestMapping != null && resourceAuth != null) {
            String[] value = requestMapping.value();
            for (String path : value) {
                ResourceAuthItem item = new ResourceAuthItem();
                item.setGroup(resourceAuth.group());
                item.setName(resourceAuth.value());
                item.setPath(toAntPath(path));
                itemList.add(item);
            }
        } else if (authMapping != null) {
            String[] value = authMapping.value();
            for (String path : value) {
                ResourceAuthItem item = new ResourceAuthItem();
                item.setGroup(authMapping.authGroup());
                item.setName(authMapping.authName());
                item.setPath(toAntPath(path));
                itemList.add(item);
            }
        }

        return itemList;
    }

    public static String toAntPath(String path) {
        return path.replaceAll("\\{[^/]+?\\}", "*");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public List<ResourceAuthItem> getResourceAuthItemList() {
        return resourceAuthItemList;
    }
}
